using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;

namespace Universe.WCF.Behaviors
{
    [Serializable]
    public class TrafficStatistic
    {
        [Serializable]
        public class Counters
        {
            public long Sent;
            public long Recieved;
            public long Requests;

            public static Counters Zero
            {
                get { return new Counters(); }
            }

            public Counters()
            {
            }

            public Counters(long sent, long recieved, long requests)
            {
                Sent = sent;
                Recieved = recieved;
                Requests = requests;
            }

            internal void Add(Counters other)
            {
                Sent += other.Sent;
                Recieved += other.Recieved;
                Requests += other.Requests;
            }

            internal static Counters Substract(Counters big, Counters small)
            {
                return new Counters(
                    Math.Max(big.Sent - small.Sent, 0L), 
                    Math.Max(big.Recieved - small.Recieved, 0L), 
                    Math.Max(big.Requests - small.Requests, 0L));
            }

            public Counters Clone()
            {
                return new Counters(Sent, Recieved, Requests);
            }

            public override string ToString()
            {
                return string.Format("Sent: {0:n0}, Recieved: {1:n0}, Requests: {2:n0}", Sent, Recieved, Requests);
            }
        }

        object Sync = new object();
        public Counters Total = new Counters();
        Dictionary<string, Counters> ByAction = new Dictionary<string, Counters>();

        public string[] GetActions()
        {
            Dictionary<string, Counters> copy;
            lock (Sync)
            {
                copy = new Dictionary<string, Counters>(ByAction);
            }

            return GetKeysSortedByCounters(copy).ToArray();
        }


        public void Merge(string action, long requests, long recieved, long sent)
        {
            lock (Sync)
            {
                AddToDictionary(ByAction, action, requests, sent, recieved);
                Total.Add(new Counters(sent, recieved, requests));
            }
        }

        public static TrafficStatistic Union(params TrafficStatistic[] list)
        {
            TrafficStatistic ret = new TrafficStatistic();
            foreach (TrafficStatistic statistic in list)
            {
                if (statistic != null)
                {
                    lock (statistic.Sync)
                    {
                        ret.Total.Add(statistic.Total);
                        
                        foreach (KeyValuePair<string, Counters> pair in statistic.ByAction)
                        {
                            string action = pair.Key;
                            Counters counters = pair.Value;
                            AddToDictionary(ret.ByAction, action, counters.Requests, counters.Sent, counters.Recieved);
                        }
                    }
                }
            }

            return ret;
        }

        public static TrafficStatistic Substract(TrafficStatistic big, TrafficStatistic small)
        {
            TrafficStatistic ret = new TrafficStatistic();
            ret.Total = Counters.Substract(big.Total, small.Total);

            foreach (KeyValuePair<string, Counters> pair in big.ByAction)
            {
                string key = pair.Key;
                Counters c = pair.Value;

                Counters other;
                small.ByAction.TryGetValue(key, out other);
                if (other != null)
                {
                    c = Counters.Substract(c, other);
                }

                ret.ByAction[key] = c;
            }


            return ret;
        }

        static List<string> GetKeysSortedByCounters(Dictionary<string, Counters> dictionary)
        {
            List<string> list = new List<string>(dictionary.Keys);
            list.Sort(
                delegate(string x, string y)
                    {
                        Counters left = dictionary[x], right = dictionary[y];
                        long ret = left.Recieved - right.Recieved + left.Sent - right.Sent;
                        return -Math.Sign(ret);
                    });

            return list;
        }

        public TrafficStatistic Clone()
        {
            TrafficStatistic ret = new TrafficStatistic();
            lock (Sync)
            {
                ret.Total.Add(Total);
                foreach (var pair in ByAction)
                    ret.ByAction[pair.Key] = pair.Value.Clone();
            }

            return ret;
        }

        public string ToPlainReport(params string[] headers)
        {
            if (headers == null) throw new ArgumentNullException("headers");

            TrafficStatistic my = Clone();
            int hl = 14;
            for (int i = 0; i < headers.Length; i += 2)
                hl = Math.Max(hl, headers[i].Length);

            StringBuilder ret = new StringBuilder();
            for (int i = 0; i < headers.Length; i += 2)
                ret.AppendFormat(" {0," + hl + "}: {1}", headers[i], headers[i + 1]).AppendLine();

            ret.AppendFormat(" {0," + hl + "}: {1:n0}", "Requests", my.Total.Requests);
            ret.AppendLine();
            ret.AppendFormat(" {0," + hl + "}: {1:n0}", "Bytes Sent", my.Total.Sent);
            ret.AppendLine();
            ret.AppendFormat(" {0," + hl + "}: {1:n0}", "Bytes recieved", my.Total.Recieved);
            ret.AppendLine();
            ret.AppendLine();

            List<string[]> raw = new List<string[]>();
            raw.Add(new string[] { "Action", "Requests", "Bytes recieved", "Bytes sent" });

            raw.Add(new string[] { new string('-', raw[0][0].Length), new string('-', raw[0][1].Length), new string('-', raw[0][2].Length), new string('-', raw[0][3].Length) });

            string[] actions = my.GetActions();
            foreach (string action in actions)
            {
                {
                    Counters v1 = my.ByAction[action];
                    raw.Add(new string[] { action, v1.Requests.ToString("n0"), v1.Recieved.ToString("n0"), v1.Sent.ToString("n0") });
                }

            }

            int[] width = new int[4];
            for (int c = 0; c < width.Length; c++)
            {
                width[c] = 0;
                foreach (string[] strings in raw)
                    width[c] = Math.Max(width[c], strings[c].Length);

            }

            for (int row = 0; row < raw.Count; row++)
            {
                for (int c = 0; c < width.Length; c++)
                {
                    string align = row == 0 || c == 0 ? "-" : "";
                    ret
                        .Append(c > 0 ? "    " : "")
                        .AppendFormat("{0," + align + (width[c]) + "}", raw[row][c]);
                }

                ret.AppendLine();
            }


            return ret.ToString();
        }

        public String ToPlainReport()
        {
            return ToPlainReport(
                "Application",
                Path.GetFileName(Assembly.GetEntryAssembly().Location));
        }

        private static void AddToDictionary(Dictionary<string, Counters> dictionary, string key, long requests, long sent, long recieved)
        {
            Counters countersByAction;
            if (!dictionary.TryGetValue(@key, out countersByAction))
            {
                countersByAction = new Counters();
                countersByAction.Requests = requests;
                countersByAction.Recieved = recieved;
                countersByAction.Sent = sent;
                dictionary[@key] = countersByAction;
            }
            else
            {
                countersByAction.Sent += sent;
                countersByAction.Recieved += recieved;
                countersByAction.Requests += requests;
            }
        }
    }
}